//
//  MPSNeuralNetworkTypes.h
//  MPS
//
//  Created by Ian Ollmann on 8/20/16.
//  Copyright © 2016 Apple. All rights reserved.
//

#ifndef MPSNeuralNetworkTypes_h
#define MPSNeuralNetworkTypes_h

#import <MPSCore/MPSCoreTypes.h>


#ifdef __cplusplus
extern "C" {
#endif
    
@class MPSImage;
@class MPSImageDescriptor;
@class MPSState;
@class MPSKernel;
@class MPSCNNKernel;
    
/*! @enum       MPSCNNConvolutionFlags
 *  @abstract   Options used to control how kernel weights are stored and used in the CNN kernels.
 *              For future expandability.
 */
#if defined(DOXYGEN)
typedef enum MPSCNNConvolutionFlags
#else
typedef NS_ENUM(NSUInteger, MPSCNNConvolutionFlags)
#endif
{
    /*! Use default options */
    MPSCNNConvolutionFlagsNone      MPS_ENUM_AVAILABLE_STARTING_BUT_DEPRECATED( "Use the Convolution -init method with a MPSCNNConvolutionDataSource instead.", ios(10.0, 11.0), tvos(10.0, 11.0)) MPS_SWIFT_NAME(none)  = 0,
}
#if defined(DOXYGEN)
    MPSCNNConvolutionFlags
#endif
;

#pragma mark -
#pragma mark MPSCNNBinaryConvolutionFlags
    
    /*! @enum       MPSCNNBinaryConvolutionFlags
     *  @abstract   Options used to control CNN Binary convolution kernels.
     */
#if defined(DOXYGEN)
typedef enum MPSCNNBinaryConvolutionFlags
#else
typedef NS_ENUM(NSUInteger, MPSCNNBinaryConvolutionFlags)
#endif
{
    /*! Use default in binary convolution options */
    MPSCNNBinaryConvolutionFlagsNone            MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  MPS_SWIFT_NAME(none)  = 0,
    /*! Scale the binary convolution operation using the beta-image option as detailed in MPSCNNBinaryConvolution */
    MPSCNNBinaryConvolutionFlagsUseBetaScaling  MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))   = 1 << 0,
}
#if defined(DOXYGEN)
    MPSCNNBinaryConvolutionFlags
#endif
    ;
    
    
    /*! @enum       MPSCNNBinaryConvolutionType
     *  @abstract   Defines what operations are used to perform binary convolution
     */
#if defined(DOXYGEN)
typedef enum MPSCNNBinaryConvolutionType
#else
typedef NS_ENUM(NSUInteger, MPSCNNBinaryConvolutionType)
#endif
{
    /*! Otherwise a normal convolution operation, except that the weights are binary values */
    MPSCNNBinaryConvolutionTypeBinaryWeights    MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  MPS_SWIFT_NAME(binaryWeights)  = 0,
    /*! Use input image binarization and the XNOR-operation to perform the actual convolution - See MPSCNNBinaryConvolution for details  */
    MPSCNNBinaryConvolutionTypeXNOR  MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0)),
    /*! Use input image binarization and the AND-operation to perform the actual convolution - See MPSCNNBinaryConvolution for details */
    MPSCNNBinaryConvolutionTypeAND  MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0)),
}
#if defined(DOXYGEN)
    MPSCNNBinaryConvolutionType
#endif
;


    
/*! @enum       MPSNNPaddingMethod
 *  @abstract   How to pad MPSNNGraph image nodes
 *  @discussion The MPSNNGraph must make automatic decisions about how big to make the result
 *              of each filter node. This is typically determined by a combination of input
 *              image size, size of the filter window (e.g. convolution weights), filter stride,
 *              and a description of how much extra space beyond the edges of the image to allow 
 *              the filter read. By knowing the properties of the filter, we can then infer 
 *              the size of the result image.  Most of this information is known to the MPSNNGraph
 *              as part of its normal operation. However, the amount of padding to add and where
 *              to add it is a matter of choice left to you, the developer. Different neural network
 *              frameworks such as TensorFlow and Caffe make different choices here.  Depending on where
 *              your network was trained, you will need to adjust the policies used by MPS during inference. 
 *              In the event that the padding method is not simply described by this enumeration, you may
 *              provide you own custom policy definition by overriding the -destinationImageDescriptorForSourceImages:
 *              sourceStates:forKernel:suggestedDescriptor: method in a custom MPSNNPadding child 
 *              class.
 *
 *              Common values that influence the size of the result image by adjusting the amount of 
 *              padding added to the source images:
 *                  - MPSNNPaddingMethodSizeValidOnly  Result values are only produced for the area that
 *                                                     is guaranteed to have all of its input values
 *                                                     defined (i.e. not off the edge).  This produces
 *                                                     the smallest result image.
 *                  - MPSNNPaddingMethodSizeSame       The result image is the same size as the input
 *                                                     image. If the stride is not 1, then the result
 *                                                     is scaled accordingly.  
 *                  - MPSNNPaddingMethodSizeFull       Result values are produced for any position for which
 *                                                     at least one input value is defined (i.e. not off
 *                                                     the edge)
 *                  - MPSNNPaddingMethodCustom     The sizing and centering policy is given by the
 *                                                     [MPSNNPadding destinationImageDescriptorForSourceImages:
 *                                                     sourceStates:forKernel:suggestedDescriptor:]
 *
 *              Except possibly when MPSNNPaddingMethodCustom is used, the area within the source 
 *              image that is read will be centered on the source image. Even so, at times the area can
 *              not be perfectly centered because the source image has odd size and the region read
 *              has even size, or vice versa. In such cases, you may use the following values to 
 *              select where to put the extra padding:
 *
 *                  - MPSNNPaddingMethodAddRemainderToTopLeft     Leftover padding is added to the top or left
 *                                                                side of image as appropriate.
 *                  - MPSNNPaddingMethodAddRemainderToBottomRight Leftover padding is added to the bottom or right
 *                                                                side of image as appropriate.
 *
 *              Here again, different external frameworks may use different policies. 
 *
 *              In some cases, Caffe intoduces the notion of a region beyond the padding which is invalid. 
 *              This can happen when the padding is set to a width narrower than what is needed for a destination
 *              size. In such cases, MPSNNPaddingMethodExcludeEdges is used to adjust normalization factors
 *              for filter weights (particularly in pooling) such that invalid regions beyond the padding are 
 *              not counted towards the filter area.  Currently, only pooling supports this feature. Other filters
 *              ignore it.
 *
 *              The MPSNNPaddingMethodSize and a MPSNNPaddingMethodAddRemainder policy always appear together
 *              in the MPSNNPaddingMethod. There is no provision for a MPSNNPaddingMethodSize without a remainder
 *              policy or vice versa. It is in practice used as a bit field.
 *
 *              Most MPSNN filters are considered forward filters. Some (e.g. convolution transpose and unpooling)
 *              are considered reverse filters. For the reverse filters, the image stride is measured in destination
 *              values rather than source values and has the effect of enlarging the image rather than reducing it.
 *              When a reverse filter is used to "undo" the effects of a forward filter, the MPSNNPaddingMethodSize should
 *              be the opposite of the forward MPSNNPaddingMethod.  For example, if the forward filter used 
 *              MPSNNPaddingMethodSizeValidOnly | MPSNNPaddingMethodAddRemainderToTopLeft, the reverse filter should use
 *              MPSNNPaddingMethodSizeFull | MPSNNPaddingMethodAddRemainderToTopLeft. Some consideration of the geometry 
 *              of inputs and outputs will reveal why this is so. It is usually not important to adjust the centering
 *              method because the size of the reverse result generally doesn't suffer from centering asymmetries. That is:
 *              the size would usually be given by:
 *              @code
 *              static int DestSizeReverse( int sourceSize, int stride, int filterWindowSize, Style style ) {
 *                  return (sourceSize-1) * stride + 1 + style  * (filterWindowSize-1);  // style = {-1,0,1} for valid-only, same, full
 *              }
 *              @endcode
 *              so the result size is exactly the one needed for the source size and there are no centering problems.
 *              In some cases where the reverse pass is intended to completely reverse a forward pass, the MPSState
 *              object produced by the forward pass should be used to determine the size of the reverse pass result
 *              image.
 *
 *              Tensorflow does not appear to provide a full padding method, but instead appears to use its valid-only
 *              padding mode for reverse filters to in effect achieve what is called MPSNNPaddingMethodSizeFull here.
 *
 *              MPSGetPaddingPolicy() is provided as a convenience to make shorter work of MPSNNPaddingMethods and policies.
 *
 *              Walkthrough of operation of padding policy:
 *              ===========================================
 *                  Most MPSCNNKernels have two types of -encode calls. There is one for which you must pass in
 *              a preallocated MPSImage to receive the results.  This is for manual configuration. It assumes you know
 *              what you are doing, and asks you to correctly set a diversity of properties to correctly position image
 *              inputs and size results. It does not use the padding policy. You must size the result correctly, set the
 *              clipRect, offset and other properties as needed yourself.
 *                  Layered on top of that is usually another flavor of -encode call that returns a destination image 
 *              instead from the left hand side of the function. It is designed to automatically configure itself based
 *              on the MPSCNNKernel.paddingPolicy. When this more automated -encode... method is called, it invokes a 
 *              method in the MPSKernel that looks at the MPSNNPaddingMethod bitfield of the policy. Based on the 
 *              information therein and the size of the input images and other filter properties, it determines the size 
 *              of the output, sets the offset property, and returns an appropriate MPSImageDescriptor for the destination 
 *              image.
 *                  If you set the MPSNNPaddingMethodCustom bit in the MPSNNPaddingMethod, then the MPSNNPadding
 *              -destinationImageDescriptorForSourceImages:sourceStates:forKernel:suggestedDescriptor: method
 *              is called. The MPSImageDescriptor prepared earlier is passed in as the last parameter. You can
 *              use this descriptor or modify as needed.  In addition, you can adjust any properties of the MPSKernel
 *              with which it will be used. If, for example, the descriptor is not the right MPSFeatureChannelFormat, you
 *              can change it, or make your own MPSImageDescriptor based on the one handed to you. This is your opportunity
 *              to customize the configuration of the MPSKernel. In some cases (e.g. MPSNNDefaultPadding.paddingForTensorflowAveragePooling
 *              you might change other properties such as the filter edging mode, or adjust the offset that was already
 *              set for you. When the kernel is fully configured, return the MPSImageDescriptor.
 *                  The MPSImageDescriptor is then passed to the MPSCNNKernel.destinationImageAllocator to allocate
 *              the image. You might provide such an allocator if you want to use your own custom MTLHeap rather 
 *              than the MPS internal heap. The allocator can be set either directly in the MPSCNNKernel or through the 
 *              MPSNNImageNode.allocator property. 
 *                  It is intended that most of the time, default values for padding method and destination image allocator
 *              should be good enough. Only minimal additional configuration should be required, apart from occasional
 *              adjustments to set the MPSNNPaddingMethod when something other than default padding for the object is
 *              needed.  If you find yourself encumbered by frequent adjustments of this kind, you might find it 
 *              to your advantage to subclass MPSNNFilterNodes or MPSCNNKernels to adjust the default padding policy and
 *              allocator at initialization time.
 *
 *
 *              tensorFlowSame = MPSNNPaddingMethodAddRemainderToBottomRight | MPSNNPaddingMethodAlignCentered | MPSNNPaddingMethodSizeSame
 */
    
#if defined(DOXYGEN)
    typedef enum MPSNNPaddingMethod
#else
    typedef NS_OPTIONS(NSUInteger, MPSNNPaddingMethod)
#endif
    {
        // The first two bits describe how to position the region read within the source image
        MPSNNPaddingMethodAlignCentered                  MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0)) MPS_SWIFT_NAME(centered)  = 0,    ///< Extra padding pixels are distributed as evenly as possible to all sides
        MPSNNPaddingMethodAlignTopLeft                   MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 1,    ///< Extra padding pixels appear on top and left sides
        MPSNNPaddingMethodAlignBottomRight               MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 2,    ///< Extra padding pixels appear on the bottom and right sides
        MPSNNPaddingMethodAlign_reserved                 MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 3,    ///< Extra padding pixels are not defined.
        MPSNNPaddingMethodAlignMask = MPSNNPaddingMethodAlign_reserved,

        
        // The next two bits describe which sides the extra padding goes in the case where the total amount of padding
        // in a dimension is not an even number and the alignment is centered. This typically happens when the filter window size is even.
        MPSNNPaddingMethodAddRemainderToTopLeft          MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0)) MPS_SWIFT_NAME(topLeft) = 0UL << 2,     ///< Extra padding pixels are accumulated to top and left sides
        MPSNNPaddingMethodAddRemainderToTopRight         MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 1UL << 2,
        MPSNNPaddingMethodAddRemainderToBottomLeft       MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 2UL << 2,
        MPSNNPaddingMethodAddRemainderToBottomRight      MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 3UL << 2,     ///< Extra padding pixels are accumulated to bottom and right sides
        MPSNNPaddingMethodAddRemainderToMask = MPSNNPaddingMethodAddRemainderToBottomRight,
        
        // The next thirteen bits describe the size of the padding area
        MPSNNPaddingMethodSizeValidOnly                 MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0)) MPS_SWIFT_NAME(validOnly) = 0,               ///< The result is the largest image for which *all* source pixels are valid for result pixels
        MPSNNPaddingMethodSizeSame                      MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 1UL << 4,        ///< The result is the same size as the input image (before strides)
        MPSNNPaddingMethodSizeFull                      MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 2UL << 4,        ///< The result is the largest image for which *any* source pixel is valid for result pixels
        MPSNNPaddingMethodSize_reserved                 MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 3UL << 4,
        MPSNNPaddingMethodCustom                        MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = (1UL << 14), ///< Use destinationImageDescriptorForSourceImages:sourceStates:forKernel:suggestedDescriptor: to calculate padding and offset.
        MPSNNPaddingMethodSizeMask                      MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = 0x7f0,

        /*! The caffe framework constrains the average pooling area to the limits of the padding area in cases
         * where a pixel would read beyond the padding area. Set this bit for Caffe emulation with average pooling.
         */
        MPSNNPaddingMethodExcludeEdges     MPS_ENUM_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))  = (1UL << 15),
    }
#if defined(DOXYGEN)
    MPSNNPaddingMethod
#endif
;

    
/*! @protocol   MPSNNPadding
 *  @abstract   A method to describe how MPSCNNKernels should pad images when data outside the image is needed
 *  @discussion Different (non-Apple) CNN frameworks have different policies for how to size the result
 *              of a CNN filter and what padding to add around the edges.  Some filters such
 *              as pooling and convolution read from neighboring feature channel (pixel) values.
 *              Four predefined MPSPaddingMethods are available: MPSNNPaddingMethodValidOnly,
 *              MPSNNPaddingMethodFull, MPSNNPaddingMethodSameTL, MPSNNPaddingMethodSameBR. You
 *              may also implement your own padding definition with a block that conforms
 *              to this prototype.
 */
@protocol MPSNNPadding <NSObject, NSSecureCoding>
    
    @required
    /*! @abstract   Get the preferred padding method for the node */
    -(MPSNNPaddingMethod) paddingMethod;
    
    @optional
    /*! A human readable string that describes the padding policy. Useful for verbose debugging support. */
    -(NSString*__nonnull) label;        // FIXME:  to be made @required
    
    /*! @abstract       Determine padding and sizing of result images
     *  @discussion     A MPSNNPaddingMethod must both return a valid MPSImageDescriptor
     *                  and set the MPSKernel.offset to the correct value.  This is a 
     *                  required feature if the MPSNNPaddingMethodCustom bit is set in
     *                  the paddingMethod.
     *
     *                  Some code that may prove helpful:
     *
     *                  @code
     *                  const int centeringPolicy = 0;  // When kernelSize is even: 0 pad bottom right. 1 pad top left.    Centers the kernel for even sized kernels.
     *
     *                  typedef enum Style{
     *                      StyleValidOnly = -1,
     *                      StyleSame = 0,
     *                      StyleFull = 1
     *                  }Style;
     *
     *                  // Typical destination size in one dimension for forward filters (most filters)
     *                  static int DestSize( int sourceSize, int stride, int filterWindowSize, Style style ){
     *                      sourceSize += style * (filterWindowSize - 1);       // adjust how many pixels we are allowed to read
     *                      return (sourceSize + stride - 1) / stride;          // sourceSize / stride, round up
     *                  }
     *
     *                  // Typical destination size in one dimension for reverse filters (e.g. unpooling, convolution transpose)
     *                  static int DestSizeReverse( int sourceSize, int stride, int filterWindowSize, Style style ){
     *                      return (sourceSize-1) * stride +        // center tap for the last N-1 results. Take stride into account
     *                              1 +                             // center tap for the first result
     *                              style * (filterWindowSize-1);   // add or subtract (or ignore) the filter extent
     *                  }
     *
     *                  // Find the MPSOffset in one dimension
     *                  static int Offset( int sourceSize, int stride, int filterWindowSize, Style style ){
     *                      // The correction needed to adjust from position of left edge to center per MPSOffset definition
     *                      int correction = filterWindowSize / 2;
     *
     *                      // exit if all we want is to start consuming pixels at the left edge of the image.
     *                      if( 0 )
     *                          return correction;
     *
     *                      // Center the area consumed in the source image:
     *                      // Calculate the size of the destination image
     *                      int destSize = DestSize( sourceSize, stride, filterWindowSize, style ); // use DestSizeReverse here instead as appropriate
     *
     *                      // calculate extent of pixels we need to read in source to populate the destination
     *                      int readSize = (destSize-1) * stride + filterWindowSize;
     *
     *                      // calculate number of missing pixels in source
     *                      int extraSize = readSize - sourceSize;
     *
     *                      // number of missing pixels on left side
     *                      int leftExtraPixels = (extraSize + centeringPolicy) / 2;
     *
     *                      // account for the fact that the offset is based on the center pixel, not the left edge
     *                      return correction - leftExtraPixels;
     *                  }
     *                  @endcode
     *
     *  @param          sourceImages        The list of source images to be used
     *  @param          sourceStates        The list of source states to be used
     *  @param          kernel              The MPSKernel the padding method will be applied to. Set the kernel.offset
     *  @param          inDescriptor        MPS will prepare a starting guess based on the padding policy (exclusive of
     *                                      MPSNNPaddingMethodCustom) set for the object. You should adjust the offset 
     *                                      and image size accordingly. It is on an autoreleasepool.
     *
     *  @return         The MPSImageDescriptor to use to make a MPSImage to capture the results from the filter. 
     *                  The MPSImageDescriptor is assumed to be on an autoreleasepool. Your method must also set the
     *                  kernel.offset property.
     */
    -(MPSImageDescriptor * __nonnull) destinationImageDescriptorForSourceImages: (NSArray <MPSImage *> *__nonnull) sourceImages
                                                                   sourceStates: (NSArray <MPSState *> * __nullable) sourceStates
                                                                      forKernel: (MPSKernel * __nonnull) kernel
                                                            suggestedDescriptor: (MPSImageDescriptor * __nonnull) inDescriptor;
@end

/*! @abstract   This class provides some pre-rolled padding policies for common tasks
 *  @discussion You are, of course, welcome to write your own class that conforms to
 *              The MPSNNPadding protocol and use that instead.
 */
MPS_CLASS_AVAILABLE_STARTING( macos(10.13), ios(11.0), tvos(11.0))
@interface MPSNNDefaultPadding : NSObject <MPSNNPadding>

/*! @abstract   Fetch a well known object that implements a non-custom padding method
 *  @discussion For custom padding methods, you will need to implement an object that conforms
 *              to the full MPSNNPadding protocol, including NSSecureCoding.
 *  @param      method  A MPSNNPaddingMethod
 *  @return     An object that implements <MPSNNPadding> for use with MPSNNGraphNodes.
 */
+ (instancetype __nonnull) paddingWithMethod: (MPSNNPaddingMethod) method;

/*! @abstract       A padding policy that attempts to reproduce TensorFlow behavior for average pooling
 *  @discussion     Most TensorFlow padding is covered by the standard MPSNNPaddingMethod encodings.
 *                  You can use +paddingWithMethod to get quick access to MPSNNPadding objects, when
 *                  default filter behavior isn't enough. (It often is.)  However, the edging for
 *                  max pooling in TensorFlow is a bit unusual.
 *
 *                  This padding method attempts to reproduce TensorFlow padding for average pooling.
 *                  In addition to setting MPSNNPaddingMethodSizeSame | MPSNNPaddingMethodAlignCentered |
 *                  MPSNNPaddingMethodAddRemainderToTopLeft, it also configures the filter to run with
 *                  MPSImageEdgeModeClamp, which (as a special case for average pooling only), normalizes the
 *                  sum of contributing samples to the area of valid contributing pixels only.
 *
 *                  @code
 *                      // Sample implementation for the tensorflowPoolingPaddingPolicy returned
 *                       -(MPSNNPaddingMethod) paddingMethod{ return MPSNNPaddingMethodCustom | MPSNNPaddingMethodSizeSame; }
 *
 *                       -(MPSImageDescriptor * __nonnull) destinationImageDescriptorForSourceImages: (NSArray <MPSImage *> *__nonnull) sourceImages
 *                                                                                      sourceStates: (NSArray <MPSState *> * __nullable) sourceStates
 *                                                                                         forKernel: (MPSKernel * __nonnull) kernel
 *                                                                               suggestedDescriptor: (MPSImageDescriptor * __nonnull) inDescriptor
 *                       {
 *
 *                          ((MPSCNNKernel *)kernel).edgeMode = MPSImageEdgeModeClamp;
 *
 *                          return inDescriptor;
 *                       }
 *                  @endcode
 */
+ (instancetype __nonnull) paddingForTensorflowAveragePooling;

/*! @abstract  Human readable description of what the padding policy does */
-(NSString * __nonnull) label;

@end
   
/*! @abstract   MPSStates conforming to this protocol contain information about a image size elsewhere in the graph
 *  @discussion In some graphs a sequence of operations are done, then they are undone ins a series of 'reverse' 
 *              operations. Examples might be pooling vs unpooling / upsampling,  or convolution vs. convolution transpose.
 *              In such cases, the 'reverse' pass generally is converting from a smaller image to a larger image,
 *              and there is insufficient information to do this correctly. Several answers exist and we don't know
 *              which is correct.
 *
 *              As an example, consider trying to 'undo' integer division with a multiplication. The expression c = a/b 
 *              is incomplete because there is also a remainder, which may constitute information lost. If we want to 
 *              reconstitute a based on c and b, we need to use a = c * b + remainder, not just a = c*b.  Similarly, when
 *              undoing a downsizing operation, we need the original size to find which answer in the range of
 *              a = c*b + [0,b-1] is the right one. */
@protocol MPSImageSizeEncodingState <NSObject>
    /*! @abstract   The width of the source image passed to MPSCNNConvolution encode call. */
    @property(readonly, nonatomic) NSUInteger       sourceWidth;
    
    /*! @abstract   The height of the source image passed to MPSCNNConvolution encode call. */
    @property(readonly, nonatomic) NSUInteger       sourceHeight;
    
@end
    
#ifdef __cplusplus
}
#endif

#endif /* MPSNeuralNetworkTypes_h */
